/*
 * Copyright (c) 2004-2006, Volatile-Engine All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Volatile-Engine nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
package com.volatileengine.renderer.lwjgl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.vecmath.Matrix4f;
import javax.vecmath.Vector2f;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;

import com.volatileengine.datatypes.AcceleratedArray;
import com.volatileengine.image.Mipmap;
import com.volatileengine.image.Texture;
import com.volatileengine.material.SceneMaterial;
import com.volatileengine.material.shader.ShaderObject;
import com.volatileengine.material.shader.ShaderProgram;
import com.volatileengine.material.shader.variable.ShaderVariable;
import com.volatileengine.platform.GPU;
import com.volatileengine.scene.Frustum;
import com.volatileengine.scene.SceneMesh;
import com.volatileengine.scene.Surface;

class LWJGLGPU implements GPU {

	private LWJGLFramebufferObjectTextureRenderer trenderer;
	private LWJGLHandleManager handleManager;
	private LWJGLShapeMeshRenderer shapeRenderer;
	private LWJGLMaterialRenderer passRenderer;

	@Override
	public void initialise() {
		try {
			Display.makeCurrent();
		} catch (LWJGLException e) {
			e.printStackTrace();
		}

		trenderer = new LWJGLFramebufferObjectTextureRenderer();
		handleManager = new LWJGLHandleManager();
		shapeRenderer = new LWJGLShapeMeshRenderer();
		passRenderer = new LWJGLMaterialRenderer();

	}

	@Override
	public void prepareSurfaces(Surface[] surfaces) {
		// tis a window
		if (surfaces.length == 1 && surfaces[0].getSurfaceCapability() == Surface.SurfaceCapability.NONE) {
			trenderer.deactivate();
			return;
		}

		// we need RT
		try {
			trenderer.initialise();
			try {
				trenderer.setSurfaces(surfaces);
			} catch (UnsupportedOperationException on) {
				trenderer.destroy();
			}
		} catch (UnsupportedOperationException ex) {
			// Logger.getLogger(LWJGLRenderer.class.getName()).log(Level.SEVERE,
			// null, ex);
			// TODO: fallback to PBuffer or glCopyTex2D...etc
		}
		trenderer.activate();

	}

	@Override
	public void showSurfaces(Surface[] surfaces) {
		// tis a window
		if (surfaces.length == 1 && surfaces[0].getSurfaceCapability() == Surface.SurfaceCapability.NONE) {
			try {
				Display.swapBuffers();
			} catch (LWJGLException ex) {
				Logger.getLogger(LWJGLGPU.class.getName()).log(Level.SEVERE, null, ex);
			}
		}

		// we're in RT otherwise
		trenderer.deactivate();
	}

	@Override
	public void viewport(Vector2f position, Vector2f dimension) {
		GL11.glViewport((int) (position.x * Display.getDisplayMode().getWidth()), (int) (position.y * Display
				.getDisplayMode().getHeight()), (int) (dimension.x * Display.getDisplayMode().getWidth()),
				(int) (dimension.y * Display.getDisplayMode().getHeight()));

	}

	@Override
	public void clear(Vector2f position, Vector2f dimension) {
		if (position != null && dimension != null) {
			GL11.glEnable(GL11.GL_SCISSOR_TEST);
			GL11.glScissor((int) (position.x * Display.getDisplayMode().getWidth()), (int) (position.y * Display
					.getDisplayMode().getHeight()), (int) (dimension.x * Display.getDisplayMode().getWidth()),
					(int) (dimension.y * Display.getDisplayMode().getHeight()));
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
			GL11.glDisable(GL11.GL_SCISSOR_TEST);
		} else {
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		}
	}

	@Override
	public void project3D(Frustum frustum) {
		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity();
		GLU.gluPerspective(frustum.getFieldOfView(), frustum.getAspectRatio(), frustum.getNearPlane(), frustum
				.getFarPlane());

		// modelview
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
		GL11.glLoadIdentity();
		loadMatrix(frustum.getTransform());
	}

	@Override
	public void draw(List<SceneMesh> mesh) {
		for (int i = 0; i < mesh.size(); i++) {
			SceneMaterial material = mesh.get(i).getSceneMaterial();
			for (int j = 0; j < material.getPasses().size(); j++) {
				passRenderer.render(material.getPasses().get(j));
				shapeRenderer.render(mesh.get(i));
			}
		}
	}

	@Override
	public void project2D(float left, float right, float bottom, float top) {
		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity();
		GL11.glOrtho(left, right, bottom, top, -1, 1);

		// modelview
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
		GL11.glLoadIdentity();
	}

	@Override
	public void createTexture(Texture tex) {
		handleManager.create(tex);
	}

	@Override
	public void createMipmap(Mipmap mipmap) {
		handleManager.create(mipmap);
	}

	@Override
	public void createVertexBufferObject(AcceleratedArray<?> array) {
		handleManager.create(array);
	}

	@Override
	public void createShaderObject(ShaderObject obj) {
		handleManager.create(obj);
	}

	@Override
	public void createShaderProgram(ShaderProgram prog) {
		handleManager.create(prog);
	}

	@Override
	public List<ShaderVariable<?>> getShaderUniforms(ShaderProgram prog) {
		return handleManager.createUniform(prog);
	}

	@Override
	public List<ShaderVariable<?>> getShaderAttributes(ShaderProgram prog) {
		return handleManager.createAttributes(prog);
	}

	private FloatBuffer matrixBuffer = ByteBuffer.allocateDirect(16 * 4).order(ByteOrder.nativeOrder())
			.asFloatBuffer();

	private void loadMatrix(Matrix4f mat) {
		if (mat != null) {
			matrixBuffer.clear();
			matrixBuffer.put(mat.m00).put(mat.m10).put(mat.m20).put(mat.m30);
			matrixBuffer.put(mat.m01).put(mat.m11).put(mat.m21).put(mat.m31);
			matrixBuffer.put(mat.m02).put(mat.m12).put(mat.m22).put(mat.m32);
			matrixBuffer.put(mat.m03).put(mat.m13).put(mat.m23).put(mat.m33);
			matrixBuffer.rewind();
			GL11.glLoadMatrix(matrixBuffer);

		}
	}
}
